--[[ A list of common auxiliary functions that did not fit in any category or weren't big enough to 
justify a module of their own. ]]
module(..., package.seeall)


local str_fmt = string.format
local io_write = io.write
local dbg_getinfo = debug.getinfo
local min = math.min
local max = math.max

function math.clamp(x, lo, hi)
    --[[ Clamp a value 'x' using the interval '[lo, hi]'. ]]
    return min(hi, max(lo, x))
end

function math.tointeger(x)
	--[[ Return only the integer part of a number. ]]
	return x - x % 1
end

function math.isinteger(x)
	--[[ Returns true if the 'x' is an integer number. ]]
	return x % 1 == 0
end

function printf(fmt, ...)
    --[[ A shortcut to use string formatting when printing text to the standard output stream. ]]
    io_write(str_fmt(fmt, ...))
end

function conditional(condition, x, y)
    --[[ Use an if statement as an expression (like the C conditional expression). This can be 
    emulated in Lua for most common situations, e.g. 
        result = condition and x or y
    would be equivalent to
        result = conditional(condition, x, y)
    but great care must be taken when 'x' has a value of false or nil. Therefore, this function 
    exists both for clarity and to avoid errors in such cases. ]]
    if condition then return x else return y end
end

function funcname(func)
    --[[ Get the name of the argument function. ]]
    return dbg_getinfo(func, "n").name
end

function import(module_name, ...)
    --[[ Same as import_into(), with the target table being the environment of this function's 
    caller. ]]
    local caller = debug.getinfo(2, "f").func
    return import_into(getfenv(caller), module_name, ...)
end

function import_into(target, module_name, ...)
    --[[ This function imports the argument module (specified by name) and makes a list of names 
    from the module available in a target table (could be for example the environment of a function 
    or a module table). This avoids the tedious task of writing long lists of assignments to local 
    variables. If no variable names are supplied after the module name, this function imports all 
    names defined in the module whose name does not start with an underscore. ]]
    local module = require(module_name)
    local names = {...}
    if #names == 0 then
        for name, _ in pairs(module) do
            if name:sub(1, 1) ~= "_" then  -- skip names starting with '_'
                table.insert(names, name)
            end
        end
    end
    for _, name in ipairs(names) do
        rawset(target, name, module[name])
    end
    return module
end

function reimport(module_name, ...)
    --[[ Like reload(), forces a reload of the module, and imports the specified names (or all 
    "public" names, i.e. those not starting with '_') into the caller's environment. ]]
    package.loaded[module_name] = nil
    return import(module_name, ...)
end

function reload(module_name)
    --[[ Removes the argument module from package.loaded and require()s the module afterward. Other 
    than causing a reload of the module, this function works like require(). In particular, it also
    returns the values returned by require(). ]]
    package.loaded[module_name] = nil
    return require(module_name)
end

function iter(t)
    --[[ A simple standard iteration function for use with generic for loops. Tables can specify 
    how they should be iterated by defining an '__iter()' field as a function that takes the table 
    as its only argument, and returns a result suitable for use in a generic for loop, i.e. an 
    iterator function, loop invariant, and initial value for the control variable (the second and 
    third are optional and default to nil). ]]
    local iter_fnc = t.__iter
    if iter_fnc ~= nil then
        return iter_fnc(t)
    end
    return next, t, nil
end

function notimplemented()
    --[[ This is just a shortcut to raise an error inside functions that must be redefined before 
    begin used. Usage:
        function SomeClass.some_method(arg1, arg2, ...)
            notimplemented()
        end
    ]]
    local fnc_name = debug.getinfo(2, "n").name
    error(("function '%s()' is not implemented"):format(fnc_name), 2)
end

_G.printf = printf
_G.conditional = conditional
_G.funcname = funcname
_G.import = import
_G.import_into = import_into
_G.reimport = reimport
_G.reload = reload
_G.iter = iter
_G.notimplemented = notimplemented
